package com.mattgarner.jaddas.node.index;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;

import com.mattgarner.jaddas.common.ColumnType;

public class ColumnIndexStatistics {

	private int columnID;
	private byte columnType;
	
	// for numeric values
	private Integer minValue;
	private Integer maxValue;
	
	// for string values
	private int cardinality;
	private int longestStringLength;
	private byte[] characterMask;
	private final char MASK_FIELD_TERMINATOR = (char)31;
	
	public ColumnIndexStatistics() {
	}
	
	public ColumnIndexStatistics(byte[] statsBytes) throws IndexException{
		if(statsBytes == null) { return; }
		ByteArrayInputStream bais = new ByteArrayInputStream(statsBytes);
		DataInputStream buffer = new DataInputStream(bais);
	
		try {
			columnID = buffer.readInt();
			columnType = buffer.readByte();
			minValue = buffer.readInt();
			maxValue = buffer.readInt();
			cardinality = buffer.readInt();
			longestStringLength = buffer.readInt();
			
			int charMaskLength = buffer.readInt();
			buffer.read(characterMask, 0, charMaskLength);
			
			buffer.close();
			bais.close();
		} catch(Exception e) {
		  throw new IndexException("parseStatsBytes: " + e.getMessage());
		}		
	}
	
	public final byte[] serializeToByteArray() {
		ByteArrayOutputStream baosStats = new ByteArrayOutputStream();
		DataOutputStream buffer = new DataOutputStream(baosStats);
		try { 
			buffer.writeInt(columnID);
			buffer.writeByte(columnType);
			
			if(minValue != null) {
				buffer.writeInt(minValue);
			} else {
				buffer.writeInt(0);
			}
			
			if(maxValue != null) {
				buffer.writeInt(maxValue);
			} else {
				buffer.writeInt(0);
			}
			
			buffer.writeInt(cardinality);
			buffer.writeInt(longestStringLength);

			if(characterMask != null) {
				buffer.writeInt(characterMask.length); 
				buffer.write(characterMask);
			} else {
				buffer.writeInt(0);
			}
			
			buffer.close();
			baosStats.close();
			byte[] statsBytes = baosStats.toByteArray();
			return statsBytes;
		} catch(IOException e) {
			// can this ever happen on a ByteArrayOutputStream?
		}
		return null;
	}
	
	public final void setColumnID(int columnID) {
		this.columnID = columnID;
	}
	
	public final int getColumnID() {
		return this.columnID;
	}
	
	public final void setColumnType(byte columnType) {
		this.columnType = columnType;
	}

	public final boolean isColumnIndexable() {
		if(this.isColumnNumeric() && this.isColumnString()) {
			return true;
		} else {
			return false;
		}
	}
	
	public final boolean isColumnNumeric() {
		if((columnType == ColumnType.COLUMN_TYPE_INT) || (columnType == ColumnType.COLUMN_TYPE_SHORT)) {
			return true;
		} else {
			return false;
		}
	}
	
	public final boolean isColumnString() {
		if(columnType == ColumnType.COLUMN_TYPE_VARCHAR) {
			return true;
		} else {
			return false;
		}
	}
	
	public final void setMinValue(int minValue) {
		this.minValue = minValue;
	}
	
	public final Integer getMinValue() {
		return this.minValue;
	}
	
	public final String getMinValueString() {
		if(this.minValue != null) {
			return this.minValue.toString();
		} else {
			return null;
		}
	}
	
	public final void setMaxValue(int maxValue) {
		this.maxValue = maxValue;
	}
	
	public final Integer getMaxValue() {
		return this.maxValue;
	}
	
	public final String getMaxValueString() {
		if(this.maxValue != null) {
			return this.maxValue.toString();
		} else {
			return null;
		}
	}
	
	public final void setCardinality(int cardinality) {
		this.cardinality = cardinality;
	}
	
	public final int getCardinality() {
		return this.cardinality;
	}
	
	public final void setLongestStringLength(int longestStringLength) {
		this.longestStringLength = longestStringLength;
	}
	
	public final int getLongestStringLength() {
		return this.longestStringLength;
	}
	
	public final void setCharacterMask(byte[] characterMap) {
		this.characterMask = characterMask;
	}
	
	public final byte[] getCharacterMask() {
		return characterMask;
	}
	
	public final void addStringToCharacterMask(String string) {
		// creates a binary mask of up to 64 characters wide which can be  
		// used to eliminate values that don't exist in the index 
		// (without requiring further searching in the index)
		
		if(characterMask == null) {
			characterMask = new byte[2048];
		}
		
		string = string.toUpperCase() + MASK_FIELD_TERMINATOR;
		char[] strChars = string.toCharArray();
		for(int a=0;(a<strChars.length && a<64);a++) {
			int colOffset = a*32;
			int byteOffset = strChars[a]/8;
			int bitOffset = strChars[a]%8;
			int bitMask = 1<<bitOffset;
			int targetByte = characterMask[colOffset+byteOffset];
			int newByte = targetByte | bitMask;
			characterMask[colOffset+byteOffset] = (byte)newByte;
		}
	}
	
	public final boolean checkStringAgainstCharacterMask(String string) {
		// checks against the binary mask whether the given string 
		// could possibly appear in the index
		
		if(characterMask == null) {
			return false;
		}
		
		string = string.toUpperCase() + MASK_FIELD_TERMINATOR;
		char[] strChars = string.toCharArray();
		for(int a=0;(a<strChars.length && a<64);a++) {
			int colOffset = a*32;
			int byteOffset = strChars[a]/8;
			int bitOffset = strChars[a]%8;
			int bitMask = 1<<bitOffset;
			int targetByte = characterMask[colOffset+byteOffset];
			int newByte = targetByte & bitMask;
			if(newByte == 0) { 
				return false; 
			} 
		}
		
		return true;
	}

}
